#12345 #####################################

stringsIntro
Strings Intro Mar10 PanosP

Introduction to Strings 

Strings in awk are any sequences of characters of any kind: letters,
digits, symbols, control characters, blanks etc. There are string
literals and variables. String literals are character sequences 
enclosed in double quotes, e.g. "panos", "abc123", "this is line
one\nthis is line 2" etc. String variables are variables that contain
strings. Even number variables may be handled as strings, it's just
a matter of concept. Let's look some examples:

  x = "abc"

Defines the variable named x having the string "abc" as a value.
That means that if we print x, the "abc" character sequence will
be printed (without the quotes, of course).

  x = 123

Defines the variable x having the number 123 as a value. But we can
use the x variable as a string, e.g.

  y = "panos" x "papadopoulos"

will put the string "panos123papadopoulos" in variable y. That is
a consequence of the string concatenation in awk which is achieved
by just puting the strings to concatenate one after the other,
without any kind of extra operator (very easy). So, if, by mistake,
we had writen:

  y = "panos" + x + "papadopoulos"

then y would have taken the number value of 123, as "panos" will
be "added" with the (number) 123 and the result will be added with
the string "papadopoulos" taken as a number. Both "panos" and
"papadopoulos" will give the value of zero if taken as numbers.
Every string not begining with digits (with the exception of white
characters) will have the numeric value of zero if taken as a number.
So:

+  "panos123" have the number value of zero, 
+ "123panos" have the number value of 123, 
+ "123.23panos" have the number value of 123.23
+ "   123panos" (with three spaces in front of 1) will have the number value of 123.

 function StringsDemo(   x, y1, y2, z1, z2, z3, z4) {
 	x = "abc"
 	x = "123"
 	y1= "panos" x "papadoupoulous"
 	y2= "panos" + x + "papadopoulos"
 	z1= "panos123" + 1
 	z2= "123paons" + 1
 	z3= "123.23panos" + 1
 	z4= "   123panos" + 1
 	print("x=["x"] y1=["y1"] z1=["z1"] z2=["z2"] z3=["z3"] z4=["z4"]")
 }   

There is a small suite of library functions for handling and
manipulating strings in awk. These functions are not so many as in
other languages, e.g. PHP, or Java, but you can easily construct
your own functions using these primitive functions.

#12345 #####################################

trimmingBlanks
Strings Sandbox PanosP

Trimming Blanks from a String 

Problem
=======

Remove leading, trailing blanks from a string.

Solution
========

Let's begin with a basic  function to trim "white" characters
from both the begining and the end of a given string:

 function trim(s) {
 	sub(/^[ \t\r\n]+/, "", s)
 	sub(/[ \t\r\n]+$/, "", s)
 	return(s)
 }

Simple enough. Now, let's add a second argument to specify the edge
where the white caracters will be trimmed. That argument will be
optional, defaulting to the  right edge (as this is often the wanted
action).  We will specify the edge(s) using some letters as mnemonics.

  #version1: we'll do it better, below
  function trimStr(s, lrb) {
  	lrb = lrb ? lrb : "r"
  	if (lrb ~ /[lLbB]/) sub(/^[ \t\r\n]+/, "", s)
  	if (lrb ~ /[rRbB]/) sub(/[ \t\r\n]+$/, "", s)
  	return(s)
  } 
 
The second argument (_lrb_) is optional. If we call this function
with only one argument, the second argument _lrb_ will be assigned
the empty string value.  If we have an empty (missing) _lrb_ argument,
we assume the default, that is trim only at the right edge (end) of the
given string (i.e. set it to "r").

(BTW, recall that all the function arguments are copied
in new, local (static) variables inside the function's scope, when
the function is called. So, setting the _lrb_ value to "r" inside the
function doesn't affect any variable outside the function's scope.)

Using these optional arguments as control variables is handy,
but there are some problems. First of
all, let's consider the dangerous situation where we pass an erroneus
_lrb_, e.g. "e" believing it's "r" (fat fingering). With a little
cost we can catch that kind of errors:

  #version2
  function trimStr(s, lrb) {
 	ok(lrb ~ /[rRlLbB]/, "trimStr: ["lrb"] bad lrb arg",2)
 	lrb = lrb ? lrb : "r"
 	if (lrb ~ /[lLbB]/) sub(/^[ \t\r\n]+/, "", s)
 	if (lrb ~ /[rRbB]/) sub(/[ \t\r\n]+$/, "", s)
 	return(s)
  }
 
We begin by checking the _lrb_ argument not to contain anything else
but the valid characters. If anything else matches, then we abort
the whole program, because that's a serious program error and must
be fixed. After that check, we can check for empty _lrb_ value by
checking _(!lrb)_ instead of the less elegant _(lrb != "")_, because
we alredy have checked the case of passing a "0" _lrb_ value by
mistake.

As a last step in constructing the final version of out _trimStr_
function, we provide the means for specifying what we mean as "white"
characters. By default we consider the spaces, tabs, carriage returns
and newlines as "white" characters. But we can also provide some
extra "white" characters if needed, e.g. vertical characters or
form feeds. To achieve such a goal, we can provide a third (optional)
argument to our _trimStr_ function which, if given, will specify the
"white" characters as a string, e.g. if we want to trim just spaces
and tabs, we can provide the _" \t"_ string:

  #version3
  function trimStr(s, lrb, wcs) {
  	ok(lrb ~ /[rRlLbB]/, "trimStr: ["lrb"] bad lrb arg",2)
  	lrb = lrb ? lrb : "r"
  	wcs = wcs ? wcs : " \t\r\n"
  	if (lrb ~ /[lLbB]/) sub("^[" wcs "]+", "", s)
  	if (lrb ~ /[rRbB]/) sub("[" wcs "]+$", "", s)
  	return(s)
  } 

Finally we came up with a small but robust function for trimming
strings. By the way, did you notice the use of dynamic regular
expressions in _sub_ calls? Beyond of robust, our little function
proves to be flexible too. Actually, we can trim characters of any
kind, not just "white" characters, using the _wcs_ argument. If, let's
say, we want to trim any non letter or underscore characters from
the begining of a string, we can just call:

  trimStr(s, "l", "^a-zA-Z_")

In order to make the function even more flexible, we can allow the _wcs_
argument to be a regular expression rather than a plain string.
Doing so, we can provide more complex strings to be trimmed out
from the string edges. The cost is that in the (rare) case where
we call the function to just trim some extra "white" characters,
we have to provide the brackets and the plus symbol too
(remind that the plus symbol is used for repetetion in RE);
but, I believe, that's a
little cost for such a great flexibility! So here, at last, is the
final version:

 # final version
 function trimStr(s, lrb, wcre) {
 	ok(lrb ~ /[rRlLbB]/, "trimStr: ["lrb"] bad lrb arg",2)
 	lrb  = lrb  ? lrb  : "r"
 	wcre = wcre ? wcre : "[ \t\r\n]+"
 	if (lrb ~ /[lLbB]/) sub("^" wcre, "", s)
 	if (lrb ~ /[rRbB]/) sub(wcre "$", "", s)
 	return(s)
 }

The following program will print all strings passed in the input
stream, after cutting out any non letter or underscore characters
from both string edges. The printed strings are printed one string
per line:

 function TrimStr(    x,y,tmp) {	
 	split("  _ 1 2 3 ,arthur", tmp, ",")
 	for(x in tmp) {
 		if (y = trimStr(x, "b", "[^a-zA-Z_]+"))
 			print y
 	}
 }

Author
======

Panos Papadopoulos

#12345 #####################################

deleteArray
Arrays Sandbox PanosP

Deleting Elements from an Array

Problem
=======

Is there a way to delete (remove) array elements, or arrays as a whole?

Solution
========

You can delete any element of an array if, of course, you know the
element's index (key), e.g. to delete the `"panos"` indexed element
of the array `users`:

  delete users["panos"]

There's no problem trying to delete an element that doesn't exist,
or even trying to delete an "element" of a non existent array,
but, if this is the case, the name of the array cannot longer be
used for any scalar or name of a function.

To delete the array `users` as a whole, just:

  delete users

that is delete the array without specify an index.

Discussion
==========

Scope of Array Names
--------------------

After deleting an array as a whole,
you cannot use the same name for a scalar variable;
the name has been "marked" as an array name by awk (permanently), so
you can only use that name for a new array, nothing else.
If you delete an array that doesn't even exist, then
the name specified is "remembered" by awk as an array,
you can't use that name for anything else but an array.

Delete First, Then Add New Entries
----------------------------------

When constructing functions that fill array elements, e.g. the
`split` builtin awk function, then don't forget to
delete the array before starting indexing new array elements.
If the array passed contains any elements
the time it's passed to the function, then these elements will,
of course, remain as array elements, mixed with the
new elements added by the function. Let's make that
point clear using an example. The code below is
a function that accepts a user name and fills
an array with information about the user just
taken from `/etc/passwd` file. The indices to
be used are:

+ uid (user id, the 3rd field)
+ gid (group id, the 4rd field)
+ info (full name, or other information, the 5th field)
+ home (home directory, the 6th field)
+ shell (shell program, the 7th field)

 function get_user_info(name, data,		ufile, ofs, found) {
 	delete data	# don't forget
 	found = 0
 	ufile = "/etc/passwd"
 	ofs = FS
 	FS = ":"
 	while ((getline <ufile) > 0) {
 		if ($1 == name) {
 			data["uid"] = $3
 			data["gid"] = $4
 			data["info"] = $5
 			data["home"] = $6
 			data["shell"] = $7
 			found = 1
 			break
 		}
 	}
 	close(ufile)
 	FS = ofs
 	return(found)
 }

Failing to call `delete data` in the beging of the funtion, the `data`
array may have elements already filled by previous calls to that same
(or other) function. Of course, you can check the returned value
(`found`) to check when the user were found or not in the password file,
but it's very risky to leave such arrays undeleted before start filling
them up.

Author
======

Panos Papadopoulos

#12345 #####################################

printArrayCsv
Arrays Sandbox PanosP

Printing an Array with Commas

Problem
=======

Print all array elements as a comma separated list.
We can print a number indexed array in an ordered
fashion, or print all elements of an associative
array in a random order.

Solution
========

 function csvArray(arr, from, to,		i, first) {
 	if (from != "") {
 		printf arr[from]
 		for (i = from + 1; i <= to; i++)
 			printf "," arr[i]
 	}
 	else {
 		first = 1
 		for (i in arr) {
 			if (first) {
 				printf arr[i]
 				first = 0
 			}
 			else
 				printf "," arr[i]
 		}
 	}
 	print ""
 }

The above function takes at most three arguments.
The first argument is, of course, the array to print.
That's a mandatory argument.
The other two arguments are optional, either none
or both of the `from` and `to` arguments must
be supplied; if supplied, then both `from`
and `to` must be numbers that specify a range of indices
of the corresponding elements of the array to print
as a comma separated list.
If the `from` and `to` arguments are missing,
then all of the array elements will be printed
(in random order) as a comma separated list.

Discussion
==========

In case of an empty array the above function may print
just a newline an nothing else.
In case of ordered print (specified `from` and `to`
arguments), if some of the indices are missing, then:

+ missing elements will be printed as empty strings.
+ missing elements will be inserted in the array, because
just the reference of an element using a specific index
will cause the insertion of the corresponding element
if it doesn't exist already (a well known sideffect of
arrays in gawk, useful most of the times).

Author
======

Panos Papadopoulos

#12345 #####################################

checkArrayIdx
Arrays Sandbox PanosP

Checking if a Key Is in an Array

Problem
=======

We want to check if a given "key" is indexing
some array element in a given array.

Solution
========

Operator `in` just do the job in awk:
 
  ...
  if (x in a)
  	# x indexed element exists.
  else
  	# x indexed element doesn't exist.
 
To make a function for that simple check
is just a formal matter:

 function arrayKeyExists(arr, idx) {
 	return(idx in arr)
 }

Author
======

Panos Papadopoulos

#12345 #####################################

checkArrayVal
Arrays Sandbox PanosP

Checking if an Element Is in an Array

Problem
=======

We want to check if a given value exists
as some element's value in a given array.

Solution
========

It's not as simple as checking for key
existence. We must iterate the array elements
to tell if a given string exists as a given array
element's value:

  function arrayValueExists(arr, val,		i) {
  	for (i in arr) {
  		if (val == arr[i])
  			return(1)
  	}
  	return(0)
  }

Discussion
==========

Function returns zero if the given value is not
an element of the given array. In that case *all*
of the array elements must be visited to tell so.
In the contrary, if the value exists, then the
function returns a non zero value (1) just
when the corresponding element is visited.
It would be of much help to return the
index of the value found, but in that case
we must take care of what to return if the value
doesn't exist. However, it's a simple thing
to implement such an approach, e.g.
given an array where there is not an empty string
indexed element, the function would look like:

 function arrayValueExists(arr, val,		i) {
  	for (i in arr) {
  		if (val == arr[i])
  			return(i)
  	}
  	return("")
 }

Keep in mind, that there may exist more than one
elements with the same value. As a last step in
this topic, we'll introduce a function to return
the _number of occurences_ of the given value,
instead of just telling if the value exists:

 function arrayValueOccurences(arr, val,	i, n) {
 	for (i in arr) {
 		if (val == arr[i])
 			n++
 	}
 	return(n + 0)
 }

The above function returns the number of elements
of the specified value, or zero when no such elements
exist in the array. Note, however, that all of the
elements must be visited.

Author
======

Panos Papadopoulos

#12345 #####################################

hiloArray
Arrays Sandbox PanosP

Finding the Largest or Smallest Valued Element in an Array

Problem
=======

Given an array, find the largest or smallest valued element.

Solution
========

First we'll find the largest element of an array containig numbers.
We must be careful not to let awk compare the array elements as
strings, because 2 will be considered greater than 10 if compared
as strings.

 function findMaxNum(arr,			i, max) {
 	for (i in arr) {
 		if ((arr[i] + 0) > (max + 0))
 			max = arr[i]
 	}
 	return(max)
 }

To find the smallest number valued element is similar:

 function findMinNum(arr,			i, min) {
 	for (i in arr) {
 		if ((arr[i] + 0) < (min + 0))
 			min = arr[i]
 	}
 	return(min)
 }

None of the above functions behave well when passed
an empty array. Also, array elements that are not pure numbers,
e.g. "123abc", may produce problems or erroneus results. The
only precaution taken by the functions above is to add number
zero to both array elements and to current max or min items
to enforce number comparison.

Now let's develop a more sophisticated function to seek
the "largest" or "smallest" value of arrays of any type.
Assuming array values are in the form of _lastname_:_firstname_:_nsn_,
e.g. `Papadopoulos:Panos:032792320`, `Menzies:Tim:782445800`, etc.
We want the largest element based on `nsn` (national security number).
It would be nice to provide a _comparison_ function as an argument.
That function may take as arguments the array and two
indices to compare the correspondig elements, and return -1, 0,
or 1 when the first element is "lower" than the second,
0 if the two elements are of the same value, and 1
if the first element is greater than the second:

 function findMax(arr, cmp,		i, maxi) {
 	for (i in arr) {
 		if (maxi == "")
 			maxi = i
 		else if (cmp(arr, i, maxi) > 0)
 			maxi = i
 	}
 	return(maxi)
 }

 function cmp(arr, i, j,			a, ai) {
 	split(arr[i], ":", a)
 	ai = a[3]
 	split(arr[j], ":", a)
 	if (ai < a[3])
 		return(-1)
 	if (ai > a[3])
 		return(1)
 	return(0)
  }

Arnold Robins, the main gawk maintainer,
is working on that issue
([?http://www.eggheadcafe.com/software/aspnet/35699599/indirect-function-calls-p.aspx]),
but for the time being *there is no such thing as function pointers or so in awk*.
So we must supply the comparison procedure as
part of the main function:

  function findMaxPerson(arr,	i, maxi, maxssn, a) {
  	for (i in arr) {
  		split(arr[i], ":", a)
  		if (maxi == "") {
  			maxssn = a[3]
  			maxi = i
  		}
  		else if (a[3] > maxssn) {
  			maxssn = a[3]
  			maxi = i
  		}
  	}
  	return(maxi)
   }

Discussion
==========

We must be careful for arrays with empty indexed items.
If that's the case, then the `(maxi == "")` check must change.

Author
======

Panos Papadopoulos

#12345 #####################################

reverseArray
Arrays Sandbox PanosP

Reversing an Array

Problem
=======

Given an array, reverse the order of the elements, assuming
that array is number indexed with step of one.

Solution
========

Synopsis
--------

  reverseArray(arr	[, from [, to]])

Reverse the order of elements in a number indexed array.
Array is passed as the first argument to the function.
Array limits (the lower and higher indices) are passed
as second and third arguments. If not passed (empty)
the second argument, assume the lower index to be zero.
If not passed (empty) the third argument, assume the
high index to be the last index found in the array
steping one by one _after_ the low index.
If any of the indices passed is invalid or out
of range, then the function prints a message an exits
with a non zero exit status.

Code
----

 function reverseArray(arr, from, to,		errs, x, count) {
 	if (from == "")
 		from = 0
 	else if (from ~ /^[ \t]*[0-9]+[ \t]*$/)
 		from += 0
 	else
 		errs += warning("reverseArray: invalid `from' " \
			"index (" from ")")
 	if (to ~ /^[ \t]*[0-9]+[ \t]*$/)
 		to += 0
 	else if (to != "")
 		errs += warning("reverseArray: invalid `to' " \
			"index (" to ")")
 	notOk(errs)
 	if (!(from in arr))
 		errs += barph("reverseArray: `from' " \
			"index out of range (" from ")")
 	# If not given the upper index, the seek it stepping one
 	# by one after the low index. If there are gaps in the
 	# array (some indices are missing) the the elements after
 	# the first gap will be ignored!
 	if (to == "") {
 		for (to = from + 1; to in arr; to++)
 			;
 		to--
 	}
 	# Normally, this is the point to check for the existence
 	# of the `to' index. But we leave that free in order to
 	# give the flexibility to create new elements as needed,
 	# e.g. given the array a[1], a[2], a[3] we can call
 	# reverseArray(a, 1, 10) and produce a[1]...a[10], puting
 	# a[1] to a[10], a[2] to a[9] and a[3] to a[8].
 	if (to < from)
 		errs += barph("reverseArray: invalid index range (" \
 			from " - " to ")")
 	for (count = 0; from < to; count++) {
 		x = arr[from]
 		arr[from] = arr[to]
 		arr[to] = x
 		from++
 		to--
 	}
 	return(count)
 }

Return Value
------------

The function returns the number of switched elements.

Discussion
==========

The function gives a lot of freedom in the calling phace.
However, the most frequently form of calling the function
may be the:

  reverseArray(x, 1)

or something like that. I believe it's a good practice to
give both the lower and upper indices, but that's a matter
of taste. Another point to ponder:
Given the upper index, missing elements will
be created!

Of course, the indices given may reverse only part
of the array, e.g.

 function TestRevArr(			a, i) {
 	# Create an array of 20 elements indexed zero
 	# based.
 	for (i = 0; i < 10; i++)
 		a[i] = "element_" i
 	# Reverse only the elements 3 throuh 7.
 	reverseArray(a, 3, 7)
 	for (i = 0; i < 10; i++)
 		print i ": " a[i]
 }

will print:

  0: element_0
  1: element_1
  2: element_2
  7: element_7
  6: element_6
  5: element_5
  4: element_4
  3: element_3
  8: element_8
  9: element_9

Author
======

Panos Papadopoulos

#12345 #####################################

shuffleDeck
Arrays Sandbox PanosP

Shuffling a Deck of Cards

Problem
=======

Assuming we have an array containing a deck of cards, we want
to shuffle the deck, that is rearange the array items in a random order.

Solution
========

We'll develop a simple function and pass the _deck_ (the array of "cards")
as the first parameter to that function.
The cards' array (_deck_) is a number indexed, 0 based (0, 1, 2,...)
array, while the cards' _count_ (how many cards are in the deck)
will be passed to the function as a second parameter
for more flexibility.
So, having a classic deck of 52 cards in array `deck`, we'll call
the function as:

  shuffleDeck(deck, 52)

After calling the function, the `deck` array must have been shuffled
and ready to deal. Here is the code:

 function shuffleDeck(deck, count,		i, n, card) {
 	seed(1) 
 	for (i = 0; i < count; i++) {
 		n = int(rand() * count)
 		card = deck[i]
 		deck[i] = deck[n]
 		deck[n] = card
 	}
 }

That's all, folks!

Shuffling a Deck of Cards in Action
-----------------------------------

Here is a complete program demonstrating the `shuffleDec` function:

 function ShuffleDeck52(		deck) {
 	newDeck52(deck)
 	shuffleDeck(deck, 52)
 	dealDeck(deck, 52)
 }

Create a new deck. 52 cards will be placed in that new
deck, not shuffled by any means. Instead, the cards will
be ordered by suit and order (Ace, 2, 3,... 10, J, Q, K).
The order of suits is undefined.

 function newDeck52(deck,	suits, figure, suit, i, n) {
	delete deck
 	suits["spades"]
 	suits["clubs"]
 	suits["diamonds"]
 	suits["hearts"]
	for (i = 2; i <= 10; i++)
 		figure[i] = i
 	figure[1] = "A"
 	figure[11] = "J"
 	figure[12] = "Q"
 	figure[13] = "K"
 	for (suit in suits) {
 		for (i = 1; i <= 13; i++)
 			deck[n++] = figure[i] " of " suit
 	}
 }
  
 function dealDeck(deck, count,		i) {
 	for (i = 0; i < count; i++)
 		print deck[i]
 }

Author
======

Panos Papadopoulos

#12345 #####################################

staticVars
Variables Sandbox PanosP

Using Static Variables

Problem
=======

Can we use variables with limited scope in awk?
How can we hide a variable inside a function
or any other program block?

Solution
========

In awk there is no need to declare or define
a variable before using it. Actually, all variables
are automatically come into existence the first time we use them.
Even if we wish to declare or define the variables
to use, there are no keywords such as _int_, _char_,
_float_ as in C, or Java, to do such a thing.
At first glance, that seems to be a disadvantage for awk,
because variables used in all functions, or other
program portions, e.g. BEGIN and END actions,
share a global name scope, that is every variable used
anywhere in the program is globaly known. Surely,
that's not a good thing for a language; unwanted globalness
is the origin of all evil in software.
Do we have a major problem then? The answer is:
Of course, no!

But how can we make a variable "local",
or, using some C or Java terminology,
"static" to some portion of a program?
The truth is that there is an elegant,
indirect way to declare variables
to be "local" in awk functions.
We know that every time we call
an awk function, all the values of the
parameters passed as arguments to
the function are copied to new variables
that come into existence when the function
is called and placed in new variables
named by the function definition.
When the function returns, all these
(temporary) variables are vanished
with just one exception: arrays.
Arrays are _passed by reference_,
but that is a topic to be discussed later.
Let's see some examples.

 function testVars(x, y,z) {
 	if ((x <= 0) || (y <= 0))
 		return(0)
 	z = sqrt((x * x) + (y * y))
 	if (z > (2 * x))
 		return(z)
 	return(x)
 }

The above function is defined to be
passed two (number)
variables and return either zero, or
another (number) value, based on some
tests done on the values of `x` and `y`
passed.
If called as

  testVars(-1, 10)

the function returns zero (because `x` value is
less than zero). If called as

  testVars(3, 4)

the function returns 3 (because the `z`
is calculated to  5 and that value is
less than two times `x`, which is 6,
thus returning the value of `x`
which is 3). It's clear that `x` and
`y` are "local" variables in the
`testVars` function, that is the names
`x` and `y` are known in the scope
of the `testVars` function and have nothing
to do with other variables named `x` or
`y` anywhere outside that function.
The `x` and `y` variables come into existence
when the function is called and receive as
values the corresponding values passed
as function arguments.
However, that's not true for the `z` variable.
The `z` variable is not declared as a 
function argument, so when assigning some
value to it, the `z` variable comes into
existence and is globaly known. More, if
the `z` variable was already a global variable
when we called the `testVars` function, then
any value already assigned to (global) `z` variable
is replaced by the expression calculated inside
`testVars`.

How can we avoid the globalness of the
`z` variable and localize the scope of `z`
inside the `testVars` function?
There is a simple solution to that kind
of problem, due to the freedom of the
arguments count to be passed in awk functions.
Actually, we can declare a function to accept
a specific number of arguments, but we
can call the function with less arguments than
those declared in the function's definition!
That's the key in creating "local" or "static"
variables to be known only in a function's
scope. Let the `testVars` function be declared as:
 
  function testVars(x, y,		z) {
  	if ((x <= 0) || (y <= 0))
  		return(0)
  	z = sqrt((x * x) + (y * y))
  	if (z > (2 * x))
  		return(z)
  	return(x)
  }

Declaring `z` as a function argument
the `z` variable become "static" in the `testVars`
function's scope. The above function definition
states that the `testVars` function accepts three
arguments (named as `x`, `y` and `z`), but we
use to call the function with only two of the
three declared arguments. That simple assumption
has a simple side effect of declaring local
`x`, `y` and `z` variables and the two values
passed when calling the function
are placed in `x` and `y` variables. We don't pass
a thrid variable, so the `z` variable is created
but not take any value (remains empty), but we can
use `z` variable in the function's code to store some values.

Discussion
==========

The method of creating local (static) function
variables just by declaring the desired variables
as (unpassed) function arguments is a very clever
idea of the awk creators. BTW, when passing arrays
as function arguments, we must have in mind that
the array elements are not copied; they just exist
with the same indices. If any new elements are
created during the function execution, or some elements
are deleted, then the array passed is affected, no
array elements are copied to static variables,
just the name of the array is to be new.
To give an example on passing arrays as
functon arguments we'll develop a function to
double all the values of the array passed.
We also add a new element indexed "sum" to
hold the doubled values' total, and return
the count of the array elements.

  function testArr(x,		i, n) {
  	delete x["sum"]
  	for (i in x) {
  		x[i] *= 2
  		x["sum"] += x[i]
  		n++
  	}
  	return(n)
  }

We define the function to accept _three_ values
as arguments, but `i`, and `n` will not actually
be passed when calling the function (that's the reason
to put some extra tabs before `i` and `n`, it's only
to emphasize the fact that `i` and `n` may not be passed,
but rather are variables to be used as static in `testArr`).
We first delete the "sum" indexed element (in case that
element already exists), and then iterate all elements
in the array passed using the (local) `i` variable
as the runing index. We double each element's value
and add the doubled value to the (recreated)
`x&#91;"sum"&#93;` element. We also increase the counter `n`,
also declared as local in `testArr`.

After visiting all the array elements, we
return the count of the elements visited.
However, there is a bug in the function.
We deleted the `x&#91;sum"&#93;` element in the
begining of the function, but we recreate the
element in the first loop pass.
Doing so, we'll visit that element in some
future loop pass and thus get erroneus results.
To fix that bug we declare another local
variable to hold the total, while the loop is on,
and create the "sum" indexed element after
visiting all the array elements:

  function testArr(x,		i, n, tot) {
  	delete x["sum"]
  	for (i in x) {
  		x[i] *= 2
  		tot += x[i]
  		n++
  	}
  	x["sum"] = tot
  	return(n)
  }

That's all folks!

Author
======

Panos Papadopoulos

#12345 #####################################

arrayHTML
Arrays Sandbox PanosP

Printing an Array in a Horizontally Columned HTML Table

Problem
=======

We want to print array elements as an HTML table.

Solution
========

That's a straight forward task:

 function arrayTable(arr, tstyle, dstyle,		i, header) {
 	# dstyle is used for styling <td> data (table cells).
 	if (!dstyle)
 		dstyle = "style=\"border-style: inset; " \
			"border-width: 2px;\""
 	for (i in arr) {
 		if (!header) {
 			# tstyle is used for styling the table as a whole.
 			print "<table " (tstyle ? tstyle : \
 				"style=\"border-style: outset; " \
				"border-width: 1px;\"") ">"
 			header = 1
 		}
 		print "\t<tr>"
 		print "\t\t<td " dstyle ">"
 		print i
 		print "\t\t</td>"
 		print "\t\t<td " dstyle ">"
 		print arr[i]
 		print "\t\t</td>"
 		print "\t</tr>"
 	}
 	if (header)
 		print "</table>"
 }

Discussion
==========
Array elements are printed in random order.
To print an array in an ordered fashion we must
sort the array and _then_ use a function similar to the
above function to print the array. Of course, that
kind of function must iterate a number indexed array.

The `tstyle` and `dstyle` arguments are optional. If given,
they will be used as given, e.g.

  arrayTable(a, "class=\"tbarr\"", "class=\"arrel\"")

If not given, then a default styling is used. To avoid the default
styling at all we can supply arguments that contain just a space,
e.g. to avoid the inner syling (no border for table elements):

  arrayTable(a, "", " ")

In the above call we supply a space as the `dstyle` argument,
causing the `if&nbsp;(!dstyle)` check to fail, while we keep the
default styling for the table as a whole by supplying an empty
argument for `tstyle`, causing the `(tstyle&nbsp;?&hellip;` check
to fail, and thus use the default cell styling.

Author
======

Panos Papadopoulos
